package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

/**
 * 全局id生成器--redis自增
 */
@Component
public class RedisIdWorker {
    /**
     *常量： 开始时间戳：2022-01-01 00:00:00=>1640995200L
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;
    /**
     *常量： 序列号的位数
     */
    private static final int COUNT_BITS = 32;

    //构造方法注入StringRedisTemplate
    private StringRedisTemplate stringRedisTemplate;
    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    //生成全局id
    public long nextId(String keyPrefix) {
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC); //获取当前时间的秒数，UTC时区的秒数，类似上面的BEGIN_TIMESTAMP
        long timestamp = nowSecond - BEGIN_TIMESTAMP;  //获取当前时间的秒数与开始时间的秒数的差值，作为时间戳，放在生成的id的高31位

        // 2.生成序列号count，如果仅仅生成一个从1开始自增的值，随着时间的推移，
        // COUNT_BITS = 32 表示序列号占32位，最大值为
        // 4,294,967,295（约42亿）。一定会出现一个超过2^32的数，会出现溢出。导致出现重复的id
        //拼接上一个日期，虽然会占用几个字节，但每天结束后，值可以重置从1开始自增。
        // 2.1.获取当前日期，精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));//2022:01:01这样的日期格式
        // 2.2.自增长？increment什么作用？
        /**
         * 效果：ID唯一性：时间戳保证不同天的ID不同，序列号保证当天内ID自增。
         * 第一次调用：存入redis
         * Key: "icr:order:2023:10:05"
         * Value: "1"  --》返回的count=1
         *
         * 第二次调用：存入redis，key不变
         * Key: "icr:order:2023:10:05"
         * Value: "2"  --》返回的count=2
         *
         * 第二天第一次调用：存入redis，key改变，value重置
         * Key: "icr:order:2023:10:06"
         * Value: "1" --》返回的count=1count的值可以重复使用，因为key改变了
         *
         */
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        // 3.生成一个64位的id，并返回
        //Long类型：64位ID = 时间戳（向高移动32位） + 序列号（低32位）。
        return timestamp << COUNT_BITS | count;
    }
}